Import des packages :
library(tidyverse)
library(knitr)
library(rmarkdown)
library(markdown)
library(data.table)
library(plotly)
library(viridis)
library(hrbrthemes)
library(lubridate)
library(highcharter)
library(shiny)
library(shinydashboard)
Chargement des jeux de données :
df_anime <- fread(file='datasets/anime_filtered.csv')
df_users <- fread(file='datasets/users_filtered.csv')
# Colonne age des utilisateurs
users <- df_users[df_users$gender %in% c('Female', 'Male') & !is.null(df_users$birth_date)] %>%
select(username, gender, user_completed, user_days_spent_watching, birth_date)
users$age <- as.period(interval(start = users$birth_date, end = as.Date(now())))$year
# Dataset et nouvelles colonnes pour le graphe de densité par décennie
date_data <- df_anime %>%
rowwise() %>%
mutate(aired = str_extract_all(aired, "(None|[0-9]*-[0-9]+-[0-9]+)")[[1]][1]) %>%
ungroup() %>%
mutate(
year = as.integer(if_else(nchar(aired) == 10, substr(aired, 1, 4), NULL)),
month = as.integer(if_else(nchar(aired) == 10, substr(aired, 6, 7), NULL)),
season = if_else(month %in% c(1:3), 'Winter',
if_else(month %in% c(4:6), 'Spring',
if_else(month %in% c(7:9), 'Summer',
if_else(month %in% c(10:12), 'Autumn', NULL)))),
year_season = paste(year, season),
decade = as.factor(if_else(!is.na(year),
paste(substr(year, 1, 3), "0's", sep = ''), NULL))
) %>%
arrange(year, month)
# Valeur médiane pour les boxplots
median_val <- df_anime %>%
select(scored_by, score) %>%
filter(scored_by > 99) %>%
summarize(median_sc = median(score))
# Extraire durée en minutes
duration_data <- df_anime %>%
mutate(h = if_else(str_detect(duration, "[0-9]*(?= hr.)"),
as.numeric(str_extract(duration, "[0-9]*(?= hr.)")), 0),
m = if_else(str_detect(duration, "[0-9]*(?= min.)"),
as.numeric(str_extract(duration, "[0-9]*(?= min.)")), 0),
s = if_else(str_detect(duration, "[0-9]*(?= sec.)"),
as.numeric(str_extract(duration, "[0-9]*(?= sec.)")), 0),
duration = h*60 + m + s/60,
duration = na_if(duration, 0))
line_nb_year <- date_data %>%
filter(!is.na(year)) %>%
group_by("Year" = year) %>%
summarise(Freq = n()) %>%
ggplot(aes(x = Year, y = Freq)) +
geom_line(color = 'red', size = 1) +
theme_ipsum() +
theme(axis.title.x = element_text(size = 14),
axis.title.y = element_text(size = 14)) +
scale_x_continuous(
expand = c(0, 0),
limits = c(1910, 2017),
breaks = seq(1910, 2017, 25)
) +
scale_y_continuous(
breaks = seq(0, 1000, 250),
limits = c(0, 1000)
) +
ggtitle("Number of anime per year")
line_nb_year <- ggplotly(line_nb_year)
line_nb_year
Ce graphique (line chart) représente l’évolution du nombre d’anime sortis par année. Le line chart est une bonne représentation de l’évolution (non-cyclique) d’une variable au fil du temps. En l’occurrence, il suffit d’un coup d’œil pour réaliser que le nombre de productions ne cesse de croître au fil des années.
data_pie_type <- df_anime %>%
filter(type != "Unknown") %>%
group_by("Type" = type) %>%
summarise(Freq = n())
highchart() %>%
hc_add_series( data_pie_type, hcaes(x = Type,y = Freq, color = Type), type = "pie") %>%
hc_tooltip(borderWidth = 1.5, headerFormat = "",
pointFormat = paste("<b>Type: {point.Type}</b> ({point.percentage:.1f}%)<br><b>Count:</b> {point.y}"))
Ce diagramme circulaire permet de représenter un petit nombre de valeurs par des angles proportionnels à ces dernières. Il indique la proportion des différents types d’anime, leur part parmi l’ensemble total des anime compris dans le jeu de données. Ici, on peut constater que le type TV (= séries d’animation diffusées à la télévision) représente une majorité d’anime, à savoir 29,6% d’entre eux, contrairement aux clips musicaux dont la part est de 5,9% seulement.
score_source <- df_anime %>%
filter(airing == F & scored_by > 99) %>%
select(score, scored_by, source) %>%
ggplot(aes(x = source, y = score)) +
geom_boxplot(fill = 'deepskyblue') +
geom_hline(aes(yintercept = median_val[[1]], linetype = 'median')) +
scale_linetype_manual(name = '', values = c(median = 'dashed')) +
coord_flip() +
theme_ipsum() +
theme(
axis.title.x = element_text(size=14),
axis.title.y = element_blank(),
) +
scale_y_continuous(limits = c(0, 10)) +
ggtitle('Score Distribution by Source')
score_source <- ggplotly(score_source)
score_source
Ce graphique « boîte à moustaches » (box plot) permet d’obtenir un aperçu des données mesurées très rapidement, ici la distribution des notes par source (un anime peut être une production originale, mais il est généralement l’adaptation d’une œuvre déjà existante).
Pour chaque « boîte », et donc chaque source, nous avons : - Sa médiane (barre verticale à l’intérieure de la boîte) ; - Les premier et troisième quartiles (respectivement les bords gauche et droit de la boîte); - Les bornes inférieure et supérieure (respectivement le début et la fin du segment); - Les valeurs potentiellement aberrantes (parfois des erreurs de saisie) sont représentées par les points noirs à gauche et/ou à droite de chaque segement.
Chaque borne inférieure est déterminée par ce calcul : Q1 – (1.5 * IQR) où Q1 est le premier quartile et IQR l’écart inter-quartile (Q3 - Q1). Si la valeur minimale de la distribution est supérieure au résultat donné par ce calcul, elle fera office de borne inférieure.
Le calcul de la borne supérieure est assez similaire : Q3 + (1.5 * IQR). De la même manière, si la valeur maximale de la distribution est inférieure au résultat produit par ce calcul, elle fera office de borne supérieure.
Nous avons également ajouté une ligne en pointillé réprésentant la médiane globale, de tous les anime, indépendamment de la source.
Prenons l’example des manga numériques (digital manga) comme source, nous remarquons que la médiane se situe très à gauche de la boîte, même très proche du Q1 ou de la borne inférieure, ce qui représente une asymétrie très importante. En revanche, il ne semble y avoir aucune valeur potentiellement aberrante (pas de points noirs visibles), les bornes inférieure et supérieure sont donc déterminées respectivement par les valeurs minimum et maximum de la distribution.
Les anime adaptés de manga sont qui atteignent les notes les plus hautes, suivis de très près par les adaptation de light novels dont même le premier quartile Q1 est supérieur à la médiane globale !
score_type <- df_anime %>%
filter(airing == F & scored_by > 99) %>%
select(score, scored_by, type) %>%
ggplot(aes(x = type, y = score)) +
geom_boxplot(fill = 'deepskyblue') +
geom_hline(aes(yintercept = median_val[[1]], linetype = 'median')) +
scale_linetype_manual(name = '', values = c(median = 'dashed')) +
coord_flip() +
theme_ipsum() +
theme(
axis.title.x = element_text(size = 14),
axis.title.y = element_blank(),
) +
scale_y_continuous(limits = c(0, 10)) +
ggtitle('Score Distribution by Type')
score_type <- ggplotly(score_type)
score_type
Il s’agit du même type de graphique que ci-dessus, ainsi la logique à suivre est la même, seulement il s’agit ici de la distribution des notes par type.
Nous pouvons, par exemple, remarquer que les clips musicaux ainsi que les ONA (Original net animation, des productions directement diffusées sur internet, sur des plateformes de streaming) sont généralement moins bien notés que les autres. D’autre part, les séries d’animation diffusées à la télévision et les films s’en sortent beaucoup mieux.
rs_score_notes <- df_anime %>%
filter(airing == F & scored_by > 99) %>%
ggplot(aes(x = scored_by, y = score)) +
stat_bin_hex(bins = 50) +
theme_ipsum() +
scale_fill_viridis() +
stat_smooth(
method = 'lm',
color = 'red',
formula = y ~ log(x)
) +
theme(
axis.title.x = element_text(size=14),
axis.title.y = element_text(size=14)
) +
scale_y_continuous(
limits = c(2, 9.5),
breaks = seq(2, 9.5)
) +
labs(title = 'Relationship between Score and Scored_by', x = "Scored by # people", y = "Score")
rs_score_notes <- ggplotly(rs_score_notes)
rs_score_notes
Grâce au graphique ci-dessus, il est possible de faire correspondre à l’intensité d’une grandeur variable une gamme de tons de couleurs. Il divise le plan en hexagones qui comptent le nombre de groupes d’anime (ici un groupe = 50 animes). Il ne s’agit que d’un choix arbitraire pour représenter au mieux les données, l’intérêt ici est avant tout de montrer la relation entre le score d’un anime et le nombre de notes attribuées à ce dernier. Le graphique dégage une tendance, en effet plus un anime est noté par un grand nombre d’utilisateurs, plus il est susceptible d’être très bien noté.
score_year_tv <- date_data %>%
filter(score != 0, scored_by > 10, type == 'TV') %>%
group_by("Year" = year) %>%
summarise(avg = round(mean(score, na.rm = T), 2)) %>%
ggplot(aes(x = Year, y = avg)) +
geom_line(color = 'red', size = 1) +
theme_ipsum() +
theme(axis.title.x = element_text(size = 14),
axis.title.y = element_text(size = 14)) +
scale_x_continuous(
expand = c(0, 0),
limits = c(1960, 2017),
breaks = seq(1960, 2017, 10)
) +
scale_y_continuous(
breaks = seq(6, 7.5, 0.2),
limits = c(6, 7.5)
) +
labs(title = "Average score per year", y = "Average score")
score_year_tv <- ggplotly(score_year_tv)
score_year_tv
Ce line chart représente l’évolution des notes au cours du temps. La moyenne des notes attribuées à des productions datant des années 1978 ou 1996 est très haute, contrairement à 1965 dont la moyenne est au plus bas. On remarque également une chute notable durant les années 2010.
ratings_tv_decade <- date_data %>%
filter(!is.na(decade), type == "TV") %>%
group_by("Decade" = decade, rating, .drop = F) %>%
summarise(Freq = n()) %>%
ggplot(aes(x = Decade, y = Freq, group = rating, shape = rating, color = rating)) +
geom_line(size = 1) +
geom_point(size = 2) +
theme_ipsum() +
theme(axis.title.x = element_text(size = 14),
axis.title.y = element_text(size = 14)) +
scale_y_continuous(
breaks = seq(0, 1250, 250),
limits = c(0, 1250)
) +
ggtitle("Ratings evolution during decades")
ratings_tv_decade <- ggplotly(ratings_tv_decade)
ratings_tv_decade
Ce graphique représente l’évolution de la classification au fil des décennies. On remarque rapidement que les productions ciblant un public agé de 13 ans ou plus sont de plus en plus nombreuses.
bbl_chart <- df_anime[df_anime$popularity != 0] %>%
arrange(popularity) %>% head(n = 100) %>%
select(popularity, rank, title, scored_by, favorites, score) %>%
filter(popularity <= 100) %>%
mutate(point = (as.numeric(scored_by) * as.numeric(favorites) * as.numeric(score)) / 10^10) %>%
ggplot(aes(x = rank, y = popularity, size = point, color = popularity, text = title)) +
geom_point(alpha = 0.7) +
scale_size(range = c(0, 19)) +
scale_color_viridis() +
theme_ipsum() +
theme(
axis.title.x = element_text(size = 14),
axis.title.y = element_text(size = 14)
) +
scale_x_continuous(
limits = c(0, 100),
breaks = seq(0, 100, 10)
) +
scale_y_continuous(
limits = c(-5, 100),
breaks = seq(0, 100, 25)
) +
labs(title='TOP 100 Anime by rank and popularity', x = 'Rank', y = 'Popularity')
bbl_chart <- ggplotly(bbl_chart, tooltip = c('rank', 'popularity', 'title'))
bbl_chart
Ce bubble chart représente les 100 animes les plus populaires. Ils sont représentés en fonction de leur rang dans le classement et de leur popularité. Le choix de ce type de graphique repose également sur une troisième variable qui s’ajoute au deux précentes, une variable point dont la formule se base sur le nombre de notes attribuées, le score ainsi que le nombre d’utilisateur ayant cet anime dans ses favoris. Elle sert à déterminer la taille de chaque bulle (donc la taille minimale est fixe dans le code).
On peut constater qu’en s’appuyant les effets de ces 3 variables, Fullmetal Alchemist: Brotherhood semble être l’anime le mieux considéré par les utilisateurs de MyAnimeList (car le mieux noté, l’un des plus populaires, des plus notés et des plus représentés dans les favoris des membres).
point_plot_pop <- df_anime[df_anime$popularity != 0] %>%
arrange(popularity) %>% head(n = 100) %>%
select(title, popularity, rank) %>%
ggplot(aes(x = popularity, y = rank)) +
geom_line(color = 'black') +
geom_point(size = 2, color = 'red') +
theme_ipsum() +
theme(axis.title.x = element_text(size=14),
axis.title.y = element_text(size=14)) +
scale_x_continuous(
expand = c(0, 0),
limits = c(-1, 101),
breaks = seq(0, 100, 5)
) +
scale_y_continuous(
breaks = seq(0, 3000, 500),
limits = c(0, 3000)
) +
ggtitle('Top 100 anime with their rank score')
point_plot_pop <- ggplotly(point_plot_pop)
point_plot_pop
Ce graphique reprend 2 des variables utilisées dans le graphique précédent, à savoir le rang et la popularité. Il représente les 100 anime les plus populaires et leur rang associé.
On remarque par exemple que le 17e anime le plus populaire est pourtant assez bas dans le classement (rang 2827).
## title popularity rank score
## 1 Sword Art Online II 17 2827 7.2
score_density_dec <- date_data %>%
select(decade, type, score) %>%
filter(!is.na(decade), type == 'TV', score != 0) %>%
ggplot(aes(score, group = decade, fill = decade)) +
geom_density(adjust = 1.25, alpha = .75) +
theme_ipsum() +
theme(
legend.position='top',
axis.title.x = element_text(size=14),
axis.title.y = element_text(size=14),
) +
scale_x_continuous(
limits = c(0, 10),
breaks = seq(0, 10, 2.5)
) +
scale_y_continuous(
expand = c(0, 0),
limits = c(0, 1),
breaks = seq(0, 1, 0.25)
) +
ggtitle('TV anime score density by decade')
score_density_dec <- ggplotly(score_density_dec)
score_density_dec
Ce graphique représente la distribution des notes par décennie. Le graphique en aires empilées peut comporte plusieurs séries. Il met à la fois l’accent sur les changements dans le temps et sur les totaux de chaque catégorie. Dans ce type de graphique une aire peut en cacher une autre, mais ici les formes des courbes permettent d’avoir un bon aperçu des valeurs mesurées.
On remarque qu’au fil des décennies, les anime ont tendance à être de mieux en mieux notés. En revanche, les notes sont plus étalées, elles sont globalement mieux réparties, contrairement aux années 1960 à 1980 qui montrent une forte concentration des notes attribuées entre 6 et 7.
score_year_movie <- date_data %>%
filter(score != 0, scored_by > 10, type == 'Movie') %>%
group_by("Year" = year) %>%
summarise(avg = round(mean(score, na.rm = T), 2)) %>%
ggplot(aes(x = Year, y = avg)) +
geom_line(color = 'red', size = 1) +
theme_ipsum() +
theme(axis.title.x = element_text(size = 14),
axis.title.y = element_text(size = 14)) +
scale_x_continuous(
expand = c(0, 0),
limits = c(1930, 2017),
breaks = seq(1930, 2017, 25)
) +
scale_y_continuous(
breaks = seq(4.5, 7.5, 0.5),
limits = c(4.5, 7.5)
) +
labs(title = "Average score per year", y = "Average score")
score_year_movie <- ggplotly(score_year_movie)
score_year_movie
ratings_movie_decade <- date_data %>%
filter(!is.na(decade), type == "Movie") %>%
group_by("Decade" = decade, rating, .drop = F) %>%
summarise(Freq = n()) %>%
ggplot(aes(x = Decade, y = Freq, group = rating, shape = rating, color = rating)) +
geom_line(size = 1) +
geom_point(size = 2) +
theme_ipsum() +
theme(axis.title.x = element_text(size = 14),
axis.title.y = element_text(size = 14)) +
scale_y_continuous(
breaks = seq(0, 300, 50),
limits = c(0, 300)
) +
ggtitle("Ratings evolution during decades")
ratings_movie_decade <- ggplotly(ratings_movie_decade)
ratings_movie_decade
score_density_movie <- date_data %>%
filter(!is.na(decade), type == 'Movie', score != 0) %>%
select(decade, type, score) %>%
ggplot(aes(score, group = decade, fill = decade)) +
geom_density(adjust = 1.25, alpha = .7) +
theme_ipsum() +
theme(
legend.position='top',
axis.title.x = element_text(size=14),
axis.title.y = element_text(size=14),
) +
scale_x_continuous(
limits = c(0, 10),
breaks = seq(0, 10, 2.5)
) +
scale_y_continuous(
expand = c(0, 0),
limits = c(0, 1),
breaks = seq(0, 1, 0.25)
) +
ggtitle("Movies score density by decade")
score_density_movie <- ggplotly(score_density_movie)
score_density_movie
box_gender_age <- users %>%
ggplot(aes(x = gender, y = age, fill = gender)) +
geom_boxplot() +
theme_ipsum() +
theme(
legend.position='none',
axis.title.x = element_text(size=14),
axis.title.y = element_text(size=14)
) +
scale_y_continuous(
expand = c(0, 0),
limits = c(-1, 60),
breaks = seq(0, 60, 5)
) +
ylab("Age")
box_gender_age <- ggplotly(box_gender_age)
box_gender_spent <- users %>%
ggplot(aes(x = gender, y = user_days_spent_watching, fill = gender)) +
geom_boxplot() +
theme_ipsum() +
theme(
legend.position = 'none',
axis.title.x = element_text(size=14),
axis.title.y = element_text(size=14)
) +
scale_y_continuous(
expand = c(0, 0),
limits = c(-1, 301),
breaks = seq(0, 300, 50)
) +
labs(title = "Users' age and days spent by gender", y = "Time spent (in days)")
box_gender_spent <- ggplotly(box_gender_spent)
subplot <- subplot(box_gender_age, box_gender_spent, nrows=1, titleY = T, margin = 0.07)
subplot
scatter_age <- users %>%
ggplot(aes(x = age, y = user_days_spent_watching, color = gender)) +
stat_bin_hex(bins = 75, alpha = 0.6) +
scale_color_manual(values= c('red', 'blue')) +
theme_ipsum() +
theme(
legend.position = 'none',
axis.title.x = element_text(size=14),
axis.title.y = element_text(size=14),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank()
) +
scale_x_continuous(
expand = c(0, 0),
limits = c(-1, 81),
breaks = seq(0, 80, 5)
) +
scale_y_continuous(
expand = c(0, 0),
limits = c(-1, 1001),
breaks = seq(0, 1000, 200)
) +
labs(title = "Anime watchers' age", x = "Age", y = "Time spent (in days)")
scatter_age <- ggplotly(scatter_age)
scatter_age